home *** CD-ROM | disk | FTP | other *** search
/ Workbench Add-On / Workbench Add-On - Volume 1.iso / Text / Show / Less / less-252 / lesskey.c < prev    next >
C/C++ Source or Header  |  1994-11-07  |  14KB  |  676 lines

  1. /*
  2.  * Copyright (c) 1984,1985,1989,1994  Mark Nudelman
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  * 1. Redistributions of source code must retain the above copyright
  9.  *    notice, this list of conditions and the following disclaimer.
  10.  * 2. Redistributions in binary form must reproduce the above copyright
  11.  *    notice in the documentation and/or other materials provided with 
  12.  *    the distribution.
  13.  *
  14.  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY
  15.  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  16.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 
  17.  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE
  18.  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
  19.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT 
  20.  * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 
  21.  * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
  22.  * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE 
  23.  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN 
  24.  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  25.  */
  26.  
  27.  
  28. /*
  29.  *    lesskey [-o output] [input]
  30.  *
  31.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  32.  *
  33.  *    Make a .less file.
  34.  *    If no input file is specified, standard input is used.
  35.  *    If no output file is specified, $HOME/.less is used.
  36.  *
  37.  *    The .less file is used to specify (to "less") user-defined
  38.  *    key bindings.  Basically any sequence of 1 to MAX_CMDLEN
  39.  *    keystrokes may be bound to an existing less function.
  40.  *
  41.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  42.  *
  43.  *    The input file is an ascii file consisting of a 
  44.  *    sequence of lines of the form:
  45.  *        string <whitespace> action [chars] <newline>
  46.  *
  47.  *    "string" is a sequence of command characters which form
  48.  *        the new user-defined command.  The command
  49.  *        characters may be:
  50.  *        1. The actual character itself.
  51.  *        2. A character preceded by ^ to specify a
  52.  *           control character (e.g. ^X means control-X).
  53.  *        3. A backslash followed by one to three octal digits
  54.  *           to specify a character by its octal value.
  55.  *        4. A backslash followed by b, e, n, r or t
  56.  *           to specify \b, ESC, \n, \r or \t, respectively.
  57.  *        5. Any character (other than those mentioned above) preceded 
  58.  *           by a \ to specify the character itself (characters which
  59.  *           must be preceded by \ include ^, \, and whitespace.
  60.  *    "action" is the name of a "less" action, from the table below.
  61.  *    "chars" is an optional sequence of characters which is treated
  62.  *        as keyboard input after the command is executed.
  63.  *
  64.  *    Blank lines and lines which start with # are ignored, 
  65.  *    except for the special control lines:
  66.  *        #line-edit    Signals the beginning of the line-editing
  67.  *                keys section.
  68.  *        #stop        Stops command parsing in less;
  69.  *                causes all default keys to be disabled.
  70.  *
  71.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  72.  *
  73.  *    The output file is a non-ascii file, consisting of a header,
  74.  *    one or more sections, and a trailer.
  75.  *    Each section begins with a section header, a section length word
  76.  *    and the section data.  Normally there are three sections:
  77.  *        CMD_SECTION    Definition of command keys.
  78.  *        EDIT_SECTION    Definition of editing keys.
  79.  *        END_SECTION    A special section header, with no 
  80.  *                length word or section data.
  81.  *
  82.  *    Section data consists of zero or more byte sequences of the form:
  83.  *        string <0> <action>
  84.  *    or
  85.  *        string <0> <action|A_EXTRA> chars <0>
  86.  *
  87.  *    "string" is the command string.
  88.  *    "<0>" is one null byte.
  89.  *    "<action>" is one byte containing the action code (the A_xxx value).
  90.  *    If action is ORed with A_EXTRA, the action byte is followed
  91.  *        by the null-terminated "chars" string.
  92.  *
  93.  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 
  94.  */
  95.  
  96. #include "less.h"
  97. #include "lesskey.h"
  98. #include "cmd.h"
  99.  
  100. struct cmdname
  101. {
  102.     char *cn_name;
  103.     int cn_action;
  104. };
  105.  
  106. struct cmdname cmdnames[] = 
  107. {
  108.     "back-bracket",        A_B_BRACKET,
  109.     "back-line",        A_B_LINE,
  110.     "back-line-force",    A_BF_LINE,
  111.     "back-screen",        A_B_SCREEN,
  112.     "back-scroll",        A_B_SCROLL,
  113.     "back-search",        A_B_SEARCH,
  114.     "back-window",        A_B_WINDOW,
  115.     "debug",        A_DEBUG,
  116.     "display-flag",        A_DISP_OPTION,
  117.     "display-option",    A_DISP_OPTION,
  118.     "end",            A_GOEND,
  119.     "examine",        A_EXAMINE,
  120.     "first-cmd",        A_FIRSTCMD,
  121.     "firstcmd",        A_FIRSTCMD,
  122.     "flush-repaint",    A_FREPAINT,
  123.     "forw-bracket",        A_F_BRACKET,
  124.     "forw-forever",        A_F_FOREVER,
  125.     "forw-line",        A_F_LINE,
  126.     "forw-line-force",    A_FF_LINE,
  127.     "forw-screen",        A_F_SCREEN,
  128.     "forw-scroll",        A_F_SCROLL,
  129.     "forw-search",        A_F_SEARCH,
  130.     "forw-window",        A_F_WINDOW,
  131.     "goto-end",        A_GOEND,
  132.     "goto-line",        A_GOLINE,
  133.     "goto-mark",        A_GOMARK,
  134.     "help",            A_HELP,
  135.     "index-file",        A_INDEX_FILE,
  136.     "invalid",        A_UINVALID,
  137.     "next-file",        A_NEXT_FILE,
  138.     "noaction",        A_NOACTION,
  139.     "percent",        A_PERCENT,
  140.     "pipe",            A_PIPE,
  141.     "prev-file",        A_PREV_FILE,
  142.     "quit",            A_QUIT,
  143.     "repaint",        A_REPAINT,
  144.     "repaint-flush",    A_FREPAINT,
  145.     "repeat-search",    A_AGAIN_SEARCH,
  146.     "repeat-search-all",    A_T_AGAIN_SEARCH,
  147.     "reverse-search",    A_REVERSE_SEARCH,
  148.     "reverse-search-all",    A_T_REVERSE_SEARCH,
  149.     "set-mark",        A_SETMARK,
  150.     "shell",        A_SHELL,
  151.     "status",        A_STAT,
  152.     "toggle-flag",        A_OPT_TOGGLE,
  153.     "toggle-option",    A_OPT_TOGGLE,
  154.     "undo-hilite",        A_UNDO_SEARCH,
  155.     "version",        A_VERSION,
  156.     "visual",        A_VISUAL,
  157.     NULL,            0
  158. };
  159.  
  160. struct cmdname editnames[] = 
  161. {
  162.     "back-complete",    EC_B_COMPLETE,
  163.     "backspace",        EC_BACKSPACE,
  164.     "delete",        EC_DELETE,
  165.     "down",            EC_DOWN,
  166.     "end",            EC_END,
  167.     "expand",        EC_EXPAND,
  168.     "forw-complete",    EC_F_COMPLETE,
  169.     "home",            EC_HOME,
  170.     "insert",        EC_INSERT,
  171.     "invalid",        EC_UINVALID,
  172.     "kill-line",        EC_LINEKILL,
  173.     "left",            EC_LEFT,
  174.     "literal",        EC_LITERAL,
  175.     "right",        EC_RIGHT,
  176.     "up",            EC_UP,
  177.     "word-backspace",    EC_W_BACKSPACE,
  178.     "word-delete",        EC_W_DELETE,
  179.     "word-left",        EC_W_RIGHT,
  180.     "word-right",        EC_W_LEFT,
  181.     NULL,            0
  182. };
  183.  
  184. struct table
  185. {
  186.     struct cmdname *names;
  187.     char *pbuffer;
  188.     char buffer[MAX_USERCMD];
  189. };
  190.  
  191. struct table cmdtable;
  192. struct table edittable;
  193. struct table *currtable = &cmdtable;
  194.  
  195. char fileheader[] = {
  196.     C0_LESSKEY_MAGIC, 
  197.     C1_LESSKEY_MAGIC, 
  198.     C2_LESSKEY_MAGIC, 
  199.     C3_LESSKEY_MAGIC
  200. };
  201. char filetrailer[] = {
  202.     C0_END_LESSKEY_MAGIC, 
  203.     C1_END_LESSKEY_MAGIC, 
  204.     C2_END_LESSKEY_MAGIC
  205. };
  206. char cmdsection[] =    { CMD_SECTION };
  207. char editsection[] =    { EDIT_SECTION };
  208. char endsection[] =    { END_SECTION };
  209.  
  210. char *infile = NULL;
  211. char *outfile = NULL ;
  212.  
  213. int linenum;
  214. int errors;
  215.  
  216. extern char version[];
  217.  
  218. /*
  219.  * Figure out the name of a default file (in the user's HOME directory).
  220.  */
  221.     char *
  222. homefile(filename)
  223.     char *filename;
  224. {
  225.     char *p;
  226.     char *pathname;
  227.  
  228.     p = getenv("HOME");
  229.     if (p == NULL || *p == '\0')
  230.     {
  231.         fprintf(stderr, "cannot find $HOME - using current directory\n");
  232.         pathname = calloc(strlen(filename) + 1, sizeof(char));
  233.         strcpy(pathname, filename);
  234.     } else
  235.     {
  236.         pathname = calloc(strlen(filename) + strlen(p) + 2, sizeof(char));
  237.         strcpy(pathname, p);
  238. #if MSOFTC
  239.         strcat(pathname, "\\");
  240. #else
  241.         strcat(pathname, "/");
  242. #endif
  243.         strcat(pathname, filename);
  244.     }
  245.     return (pathname);
  246. }
  247.  
  248. /*
  249.  * Parse command line arguments.
  250.  */
  251.     void
  252. parse_args(argc, argv)
  253.     int argc;
  254.     char **argv;
  255. {
  256.     outfile = NULL;
  257.     while (--argc > 0 && **(++argv) == '-' && argv[0][1] != '\0')
  258.     {
  259.         switch (argv[0][1])
  260.         {
  261.         case 'o':
  262.             outfile = &argv[0][2];
  263.             if (*outfile == '\0')
  264.             {
  265.                 if (--argc <= 0)
  266.                     usage();
  267.                 outfile = *(++argv);
  268.             }
  269.             break;
  270.         case 'V':
  271.             printf("lesskey  version %s\n", version);
  272.             exit(0);
  273.         default:
  274.             usage();
  275.         }
  276.     }
  277.     if (argc > 1)
  278.         usage();
  279.     /*
  280.      * Open the input file, or use DEF_LESSKEYINFILE if none specified.
  281.      */
  282.     if (argc > 0)
  283.         infile = *argv;
  284.     else
  285.         infile = homefile(DEF_LESSKEYINFILE);
  286. }
  287.  
  288. /*
  289.  * Initialize data structures.
  290.  */
  291.     void
  292. init_tables()
  293. {
  294.     cmdtable.names = cmdnames;
  295.     cmdtable.pbuffer = cmdtable.buffer;
  296.  
  297.     edittable.names = editnames;
  298.     edittable.pbuffer = edittable.buffer;
  299. }
  300.  
  301. /*
  302.  * Parse one character of a string.
  303.  */
  304.     int
  305. tchar(pp)
  306.     char **pp;
  307. {
  308.     register char *p;
  309.     register char ch;
  310.     register int i;
  311.  
  312.     p = *pp;
  313.     switch (*p)
  314.     {
  315.     case '\\':
  316.         ++p;
  317.         switch (*p)
  318.         {
  319.         case '0': case '1': case '2': case '3':
  320.         case '4': case '5': case '6': case '7':
  321.             /*
  322.              * Parse an octal number.
  323.              */
  324.             ch = 0;
  325.             i = 0;
  326.             do
  327.                 ch = 8*ch + (*p - '0');
  328.             while (*++p >= '0' && *p <= '7' && ++i < 3);
  329.             *pp = p;
  330.             return (ch);
  331.         case 'b':
  332.             *pp = p+1;
  333.             return ('\r');
  334.         case 'e':
  335.             *pp = p+1;
  336.             return (ESC);
  337.         case 'n':
  338.             *pp = p+1;
  339.             return ('\n');
  340.         case 'r':
  341.             *pp = p+1;
  342.             return ('\r');
  343.         case 't':
  344.             *pp = p+1;
  345.             return ('\t');
  346.         default:
  347.             /*
  348.              * Backslash followed by any other char 
  349.              * just means that char.
  350.              */
  351.             *pp = p+1;
  352.             return (*p);
  353.         }
  354.     case '^':
  355.         /*
  356.          * Carat means CONTROL.
  357.          */
  358.         *pp = p+2;
  359.         return (CONTROL(p[1]));
  360.     }
  361.     *pp = p+1;
  362.     return (*p);
  363. }
  364.  
  365. /*
  366.  * Skip leading spaces in a string.
  367.  */
  368.     public char *
  369. skipsp(s)
  370.     register char *s;
  371. {
  372.     while (*s == ' ' || *s == '\t')    
  373.         s++;
  374.     return (s);
  375. }
  376.  
  377. /*
  378.  * Skip non-space characters in a string.
  379.  */
  380.     public char *
  381. skipnsp(s)
  382.     register char *s;
  383. {
  384.     while (*s != '\0' && *s != ' ' && *s != '\t')
  385.         s++;
  386.     return (s);
  387. }
  388.  
  389. /*
  390.  * Clean up an input line:
  391.  * strip off the trailing newline & any trailing # comment.
  392.  */
  393.     char *
  394. clean_line(s)
  395.     char *s;
  396. {
  397.     register int i;
  398.  
  399.     s = skipsp(s);
  400.     for (i = 0;  s[i] != '\n' && s[i] != '\0';  i++)
  401.         if (s[i] == '#' && (i == 0 || s[i-1] != '\\'))
  402.             break;
  403.     s[i] = '\0';
  404.     return (s);
  405. }
  406.  
  407. /*
  408.  * Add a byte to the output command table.
  409.  */
  410.     void
  411. add_cmd_char(c)
  412.     int c;
  413. {
  414.     if (currtable->pbuffer >= currtable->buffer + MAX_USERCMD)
  415.     {
  416.         error("too many commands");
  417.         exit(1);
  418.     }
  419.     *(currtable->pbuffer)++ = c;
  420. }
  421.  
  422. /*
  423.  * See if we have a special "control" line.
  424.  */
  425.     int
  426. control_line(s)
  427.     char *s;
  428. {
  429. #define    PREFIX(str,pat)    (strncmp(str,pat,strlen(pat)-1) == 0)
  430.  
  431.     if (PREFIX(s, "#line-edit"))
  432.     {
  433.         currtable = &edittable;
  434.         return (1);
  435.     }
  436.     if (PREFIX(s, "#command"))
  437.     {
  438.         currtable = &cmdtable;
  439.         return (1);
  440.     }
  441.     if (PREFIX(s, "#stop"))
  442.     {
  443.         add_cmd_char('\0');
  444.         add_cmd_char(A_END_LIST);
  445.         return (1);
  446.     }
  447.     return (0);
  448. }
  449.  
  450. /*
  451.  * Output some bytes.
  452.  */
  453.     void
  454. fputbytes(fd, buf, len)
  455.     FILE *fd;
  456.     char *buf;
  457.     int len;
  458. {
  459.     while (len-- > 0)
  460.     {
  461.         fwrite(buf, sizeof(char), 1, fd);
  462.         buf++;
  463.     }
  464. }
  465.  
  466. /*
  467.  * Output an integer, in special KRADIX form.
  468.  */
  469.     void
  470. fputint(fd, val)
  471.     FILE *fd;
  472.     unsigned int val;
  473. {
  474.     char c;
  475.  
  476.     if (val >= KRADIX*KRADIX)
  477.     {
  478.         fprintf(stderr, "error: integer too big (%d > %d)\n", 
  479.             val, KRADIX*KRADIX);
  480.         exit(1);
  481.     }
  482.     c = val % KRADIX;
  483.     fwrite(&c, sizeof(char), 1, fd);
  484.     c = val / KRADIX;
  485.     fwrite(&c, sizeof(char), 1, fd);
  486. }
  487.  
  488. /*
  489.  * Find an action, given the name of the action.
  490.  */
  491.     int
  492. findaction(actname)
  493.     char *actname;
  494. {
  495.     int i;
  496.  
  497.     for (i = 0;  currtable->names[i].cn_name != NULL;  i++)
  498.         if (strcmp(currtable->names[i].cn_name, actname) == 0)
  499.             return (currtable->names[i].cn_action);
  500.     error("unknown action");
  501.     return (A_INVALID);
  502. }
  503.  
  504. usage()
  505. {
  506.     fprintf(stderr, "usage: lesskey [-o output] [input]\n");
  507.     exit(1);
  508. }
  509.  
  510.     void
  511. error(s)
  512.     char *s;
  513. {
  514.     fprintf(stderr, "line %d: %s\n", linenum, s);
  515.     errors++;
  516. }
  517.  
  518.  
  519. /*
  520.  * Parse a line from the lesskey file.
  521.  */
  522.     void
  523. parse_line(line)
  524.     char *line;
  525. {
  526.     char *p;
  527.     int cmdlen;
  528.     char *actname;
  529.     int action;
  530.     int c;
  531.  
  532.     /*
  533.      * See if it is a control line.
  534.      */
  535.     if (control_line(line))
  536.         return;
  537.     /*
  538.      * Skip leading white space.
  539.      * Replace the final newline with a null byte.
  540.      * Ignore blank lines and comments.
  541.      */
  542.     p = clean_line(line);
  543.     if (*p == '\0')
  544.         return;
  545.  
  546.     /*
  547.      * Parse the command string and store it in the current table.
  548.      */
  549.     cmdlen = 0;
  550.     do
  551.     {
  552.         c = tchar(&p);
  553.         if (++cmdlen > MAX_CMDLEN)
  554.             error("command too long");
  555.         else
  556.             add_cmd_char(c);
  557.     } while (*p != ' ' && *p != '\t' && *p != '\0');
  558.     /*
  559.      * Terminate the command string with a null byte.
  560.      */
  561.     add_cmd_char('\0');
  562.  
  563.     /*
  564.      * Skip white space between the command string
  565.      * and the action name.
  566.      * Terminate the action name with a null byte.
  567.      */
  568.     p = skipsp(p);
  569.     if (*p == '\0')
  570.     {
  571.         error("missing action");
  572.         return;
  573.     }
  574.     actname = p;
  575.     p = skipnsp(p);
  576.     c = *p;
  577.     *p = '\0';
  578.  
  579.     /*
  580.      * Parse the action name and store it in the current table.
  581.      */
  582.     action = findaction(actname);
  583.  
  584.     /*
  585.      * See if an extra string follows the action name.
  586.      */
  587.     *p = c;
  588.     p = skipsp(p);
  589.     if (*p == '\0')
  590.     {
  591.         add_cmd_char(action);
  592.     } else
  593.     {
  594.         /*
  595.          * OR the special value A_EXTRA into the action byte.
  596.          * Put the extra string after the action byte.
  597.          */
  598.         add_cmd_char(action | A_EXTRA);
  599.         while (*p != '\0')
  600.             add_cmd_char(tchar(&p));
  601.         add_cmd_char('\0');
  602.     }
  603. }
  604.  
  605. main(argc, argv)
  606.     int argc;
  607.     char *argv[];
  608. {
  609.     FILE *desc;
  610.     FILE *out;
  611.     char line[200];
  612.  
  613.     /*
  614.      * Process command line arguments.
  615.      */
  616.     parse_args(argc, argv);
  617.     init_tables();
  618.  
  619.     /*
  620.      * Open the input file.
  621.      */
  622.     if (strcmp(infile, "-") == 0)
  623.         desc = stdin;
  624.     else if ((desc = fopen(infile, "r")) == NULL)
  625.     {
  626.         perror(infile);
  627.         exit(1);
  628.     }
  629.  
  630.     /*
  631.      * Read and parse the input file, one line at a time.
  632.      */
  633.     errors = 0;
  634.     linenum = 0;
  635.     while (fgets(line, sizeof(line), desc) != NULL)
  636.     {
  637.         ++linenum;
  638.         parse_line(line);
  639.     }
  640.  
  641.     /*
  642.      * Write the output file.
  643.      * If no output file was specified, use "$HOME/.less"
  644.      */
  645.     if (errors > 0)
  646.     {
  647.         fprintf(stderr, "%d errors; no output produced\n", errors);
  648.         exit(1);
  649.     }
  650.  
  651.     if (outfile == NULL)
  652.         outfile = homefile(LESSKEYFILE);
  653.     if ((out = fopen(outfile, "w")) == NULL)
  654.     {
  655.         perror(outfile);
  656.         exit(1);
  657.     }
  658.  
  659.     /* File header */
  660.     fputbytes(out, fileheader, sizeof(fileheader));
  661.  
  662.     /* Command key section */
  663.     fputbytes(out, cmdsection, sizeof(cmdsection));
  664.     fputint(out, cmdtable.pbuffer - cmdtable.buffer);
  665.     fputbytes(out, (char *)cmdtable.buffer, cmdtable.pbuffer-cmdtable.buffer);
  666.     /* Edit key section */
  667.     fputbytes(out, editsection, sizeof(editsection));
  668.     fputint(out, edittable.pbuffer - edittable.buffer);
  669.     fputbytes(out, (char *)edittable.buffer, edittable.pbuffer-edittable.buffer);
  670.  
  671.     /* File trailer */
  672.     fputbytes(out, endsection, sizeof(endsection));
  673.     fputbytes(out, filetrailer, sizeof(filetrailer));
  674.     exit(0);
  675. }
  676.